﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace BrandonHaynes.Providers.AmazonS3.Data
	{
	/// <summary>
	/// A class that implements both IDataReader and the composite design pattern.  Thus, it both
	/// implements the interface and contains one or more interface-implementing objects.
	/// 
	/// Because DotNetNuke exchanges IDataReader-implementing objects at the interface between
	/// data provider and core, we are able to return a composite back into the core and effectuate
	/// the union of multiple data sets.  The core never knows the difference.
	/// </summary>
	class CompositeDataReader : IDataReader
		{
		/// <summary>
		/// A set of IDataReaders, the union of which forms the composite implementation
		/// </summary>
		private IList<IDataReader> Readers { get; set; }
		/// <summary>
		/// An enumerator; this represents the "current" IDataReader during iteration.  Because 
		/// the class is immutable, we need not be concerned with the iterated set changing
		/// while enumerating.
		/// </summary>
		private IEnumerator<IDataReader> Enumerator { get; set; }

		/// <summary>
		/// Construct a composite over an enumerable set of IDataReaders
		/// </summary>
		/// <param name="readers">The set of IDataReaders to form a composite with</param>
		public CompositeDataReader(IEnumerable<IDataReader> readers)
			{
			Readers = readers.ToList();
			Enumerator = readers.GetEnumerator(); // Begin enumeration
			Enumerator.MoveNext();
			}

		/// <summary>
		/// Construct a composite over two supplied IDataReaders
		/// </summary>
		/// <param name="first">The first IDataReader in the composite</param>
		/// <param name="second">The second IDataReader in the composite</param>
		public CompositeDataReader(IDataReader first, IDataReader second)
			: this(new List<IDataReader>(2) { first, second })
			{ }

		#region IDataReader Members

		public void Close()
			{ Readers.ForEach(reader => reader.Close()); }

		/// <summary>
		/// This may or may not cause unexpected behavior, as the depth may vary widely
		/// as different components of the composite are enumerated.
		/// </summary>
		public int Depth
			{ get { return Enumerator.Current.Depth; } }

		public DataTable GetSchemaTable()
			{ throw new NotSupportedException(); }

		public bool IsClosed
			{ get { return Readers.Any(reader => reader.IsClosed); } }

		public bool NextResult()
			{
			bool result;

			if (Enumerator.Current != null)
				{
				result = Enumerator.Current.NextResult(); // Next record, current reader
				if (!result) // Out of records for our current reader?
					{
					Enumerator.MoveNext(); // Iterate to the next IDataReader
					result = NextResult(); // And recurse, usually iterating the current reader
					}
				}
			else
				result = false;

			return result;
			}

		public bool Read()
			{
			bool result;

			if (Enumerator.Current != null)
				{
				result = Enumerator.Current.Read(); // Read from our current reader
				if (!result) // No records in our current reader?
					{
					Enumerator.MoveNext();	// Then move to the next IDataReader
					return Read();			// And recurse
					}
				}
			else
				result = false;

			return result;
			}

		public int RecordsAffected
			{ get { return Readers.Sum(reader => reader.RecordsAffected); } }

		#endregion

		#region IDisposable Members

		public void Dispose()
			{ 
			Dispose(true); 
			GC.SuppressFinalize(this);
			}

		private bool disposed;
		protected virtual void Dispose(bool disposing)
			{
			// Here we are tolerating the fact that an early exception may prevent later resources from 
			// being disposed.

			// We rely on the Framework Design Guidelines (2nd ed at §9.4.1), which require that 
			// Dispose exceptions be avoided except under critical circumstances (e.g. out of memory).  
			// And, under a critial situation, I feel it is better to bubble the immediate error than 
			// attempt to aggregate (where intermediating issues may complicate diagnosis of the
			// underlying issue -- for example, allocating aggregation stack may not be possible in an
			// out of memory environment).  
			if (!disposed && disposing)
				{
				if(Readers != null) Readers.ForEach(reader => reader.Dispose());
				if(Enumerator != null) Enumerator.Dispose();
				}

			Enumerator = null;
			Readers = null;
			disposed = true;
			}

		#endregion

		#region IDataRecord Members

		// These methods all treat the current enumerated element as a decorated object,
		// delegating all calls to that object (and failing if no current record exists).

		public int FieldCount
			{ get { return Enumerator.Current.FieldCount; } }

		public bool GetBoolean(int i)
			{ return Enumerator.Current.GetBoolean(i); }

		public byte GetByte(int i)
			{ return Enumerator.Current.GetByte(i); }

		public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
			{ return Enumerator.Current.GetBytes(i, fieldOffset, buffer, bufferoffset, length); }

		public char GetChar(int i)
			{ return Enumerator.Current.GetChar(i); }

		public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
			{ return Enumerator.Current.GetChars(i, fieldoffset, buffer, bufferoffset, length); }

		public IDataReader GetData(int i)
			{ return Enumerator.Current.GetData(i); }

		public string GetDataTypeName(int i)
			{ return Enumerator.Current.GetDataTypeName(i); }

		public DateTime GetDateTime(int i)
			{ return Enumerator.Current.GetDateTime(i); }

		public decimal GetDecimal(int i)
			{ return Enumerator.Current.GetDecimal(i); }

		public double GetDouble(int i)
			{ return Enumerator.Current.GetDouble(i); }

		public Type GetFieldType(int i)
			{ return Enumerator.Current.GetFieldType(i); }

		public float GetFloat(int i)
			{ return Enumerator.Current.GetFloat(i); }

		public Guid GetGuid(int i)
			{ return Enumerator.Current.GetGuid(i); }

		public short GetInt16(int i)
			{ return Enumerator.Current.GetInt16(i); }

		public int GetInt32(int i)
			{ return Enumerator.Current.GetInt32(i); }

		public long GetInt64(int i)
			{ return Enumerator.Current.GetInt64(i); }

		public string GetName(int i)
			{ return Enumerator.Current.GetName(i); }

		public int GetOrdinal(string name)
			{ return Enumerator.Current.GetOrdinal(name); }

		public string GetString(int i)
			{ return Enumerator.Current.GetString(i); }

		public object GetValue(int i)
			{ return Enumerator.Current.GetValue(i); }

		public int GetValues(object[] values)
			{ return Enumerator.Current.GetValues(values); }

		public bool IsDBNull(int i)
			{ return Enumerator.Current.IsDBNull(i); }
	
		public object this[string name]
			{ get { return Enumerator.Current[name]; } }

		public object this[int i]
			{ get { return Enumerator.Current[i]; } }

		#endregion
		}
	}
